﻿using System;
using System.Linq;
using ICSharpCode.NRefactory.CSharp;
using Xunit;

namespace Framework.Tests.Refactoring.InversionOfControl
{
    public class OldCode
    {
        public int GetResult()
        {
            var toto = "pouet";
            var dependency = new LegacyCode("a");
            return dependency.GetResult("s");
        }
    }

    public class LegacyCode
    {
        private readonly string s;

        public LegacyCode(string s)
        {
            this.s = s;
        }

        public int GetResult(string a)
        {
            return 10;
        }
    }

    public interface ILegacyCodeFactory
    {
        LegacyCode Create(string s);
    }

    public class LegacyCodeFactory : ILegacyCodeFactory
    {
        public LegacyCode Create(string s)
        {
            return new LegacyCode(s);
        }
    }

    public class NewCode
    {
        private readonly ILegacyCodeFactory legacyCodeFactory;

        internal NewCode(ILegacyCodeFactory legacyCodeFactory)
        {
            this.legacyCodeFactory = legacyCodeFactory;
        }

        public NewCode()
            : this(new LegacyCodeFactory())
        {

        }

        public int GetResult()
        {
            var dependency = legacyCodeFactory.Create("a");
            return dependency.GetResult("s");
        }
    }

    public class ExtractFactoryVisitor : AbstractDepthFirstAstVisitor
    {
        public override void VisitObjectCreateExpression(ObjectCreateExpression objectCreateExpression)
        {
            //TODO : Analyze dependencies for using section
            //TODO : Fix Constructor Tree
            
            var refactoringNamespace = GetRefactoringNamespace();

            var factoryType = ExtractFactory(objectCreateExpression, refactoringNamespace);

            var targetType = objectCreateExpression.GetParent<TypeDeclaration>();

            var factoryField = CreateFactoryField(factoryType, targetType);

            UpdateOrCreateConstructor(Modifiers.Internal, targetType, GetParameterDeclaration(factoryType));

            SubstituteObjectCreationByFactoryCall(objectCreateExpression, factoryField, factoryType);

            base.VisitObjectCreateExpression(objectCreateExpression);
        }

        private static void SubstituteObjectCreationByFactoryCall(ObjectCreateExpression objectCreateExpression,
                                                              FieldDeclaration factoryField, TypeDeclaration factoryType)
        {
            var factoryFieldIdentifier = new IdentifierExpression(factoryField.Variables.First().Name);
            var factoryMethodDeclaration = factoryType.Members.OfType<MethodDeclaration>().First();
            var memberName = factoryMethodDeclaration.Name;
            var arguments = objectCreateExpression.Arguments.Select(x => x.Clone());

            var memberReferenceExpression = new MemberReferenceExpression(factoryFieldIdentifier, memberName);
            var invocationExpression = new InvocationExpression(memberReferenceExpression, arguments);

            objectCreateExpression.ReplaceWith(invocationExpression);
        }

        private static ParameterDeclaration GetParameterDeclaration(TypeDeclaration factoryType)
        {
            return new ParameterDeclaration
                {
                    Name = GetFieldName(factoryType),
                    Type = factoryType.BaseTypes.First().Clone(),
                };
        }

        private static FieldDeclaration CreateFactoryField(TypeDeclaration fieldType, TypeDeclaration targetType)
        {
            var fieldName = GetFieldName(fieldType);

            return CreateDependencyField(targetType, fieldType, fieldName);
        }

        private static TypeDeclaration ExtractFactory(ObjectCreateExpression objectCreateExpression, NamespaceDeclaration refactoringNamespace)
        {
            var factoryReturnType = (SimpleType)objectCreateExpression.Descendants.First(x => x is SimpleType);

            var interfaceName = GetInterfaceName(factoryReturnType);

            if (!HasTypeDeclaration(objectCreateExpression, interfaceName))
            {
                var factoryInterface = CreateInterface(interfaceName);

                AddCreateMethodToInterface(objectCreateExpression, factoryInterface);

                var implementationName = GetImplementationName(factoryReturnType);

                if (!HasTypeDeclaration(objectCreateExpression, implementationName))
                {
                    var factoryImplementation = CreateClass(implementationName);

                    ImplementFactoryInterface(factoryImplementation, factoryInterface, objectCreateExpression);

                    var syntaxTree = objectCreateExpression.GetParent<SyntaxTree>();

                    syntaxTree.Members.Add(refactoringNamespace);

                    refactoringNamespace.Members.Add(factoryInterface);
                    refactoringNamespace.Members.Add(factoryImplementation);

                    return factoryImplementation;
                }
            }

            throw new NotImplementedException();
        }

        private static NamespaceDeclaration GetRefactoringNamespace()
        {
            return CreateNamespace("RefactorIt.Ioc.Factory");
        }

        private static void AddCreateMethodToInterface(ObjectCreateExpression objectCreateExpression,
                                                       TypeDeclaration factoryInterface)
        {
            AddMethodToInterface(factoryInterface, objectCreateExpression.Type.Clone(), "Build",
                                 GetParametersFromArguments(objectCreateExpression.Arguments.OfType<PrimitiveExpression>().ToArray()).ToArray());
        }

        private static void ImplementFactoryInterface(TypeDeclaration factoryImplementation, TypeDeclaration factoryInterface, ObjectCreateExpression objectCreateExpression)
        {
            factoryImplementation.BaseTypes.Add(new SimpleType(factoryInterface.Name));

            var method = (MethodDeclaration)factoryInterface.Members.First().Clone();

            method.Modifiers = Modifiers.Public;

            method.Body = new BlockStatement();
            var returnStatement = new ReturnStatement();
            method.Body.Add(returnStatement);
            var clonedObjectCreateExpression = (ObjectCreateExpression)objectCreateExpression.Clone();

            var methodParameters = method.Parameters.ToList();
            var arguments = clonedObjectCreateExpression.Arguments.ToList();

            for (var i = 0; i < clonedObjectCreateExpression.Arguments.Count; i++)
            {
                var methodParameter = methodParameters[i];

                arguments[i].ReplaceWith(new IdentifierExpression(methodParameter.Name));
            }

            returnStatement.Expression = clonedObjectCreateExpression;

            factoryImplementation.Members.Add(method);
        }

        private static string GetInterfaceName(SimpleType returnType)
        {
            return String.Format("I{0}", GetImplementationName(returnType));
        }

        private static string GetImplementationName(SimpleType returnType)
        {
            return String.Format("{0}Factory", returnType.IdentifierToken.Name);
        }
    }

    // ReSharper disable InconsistentNaming
    // ReSharper disable UnusedMember.Global
    public class ExtractFactoryTest
    {
        [Fact]
        public void Should_extract_factory_in_simple_case()
        {
            var parser = new CSharpParser();

            var syntaxTree = parser.Parse(@"
namespace Framework.Tests.Refactoring.InversionOfControl
{
    public class OldCode
    {
        public int GetResult()
        {
            var toto = ""pouet"";
            var dependency = new LegacyCode(""a"");
            return dependency.GetResult(""s"");
        }
    }
}
");
            Console.WriteLine("Before Refactoring");
            Console.WriteLine(syntaxTree.GetText());

            syntaxTree.AcceptVisitor(new ExtractFactoryVisitor());

            Console.WriteLine("After Refactoring");
            Console.WriteLine(syntaxTree.GetText());
        }
    }
    // ReSharper restore InconsistentNaming
    // ReSharper restore UnusedMember.Global
}
